<article id="wikiArticle">
<p></p><div class="blockIndicator draft">
<p><strong>草案</strong><br/>
    本页尚未完工.</p>
</div><p></p>
<p><span></span></p><p></p>
<p>每个JavaScript对象都拥有一个<code>[[Prototype]]</code>对象。  获取一个对象的属性时首先会搜索其自身, 然后就是它的 <code>[[Prototype]]</code>对象, 之后再搜索此<code>[[Prototype]]</code>对象的 <code>[[Prototype]]</code>对象, 直到找到这个属性或者搜索链条达到终点. 这个类似链条的查找过程被称为原型链。  原型链在<a href="/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">对象继承</a>中非常重要。</p>
<p>ECMAScript 6 引入了一种方式来修改 <code>[[Prototype]]</code>对象。 提升了灵活性的代价是降低了性能。 <strong>修改<code>[[Prototype]]</code> 对象会损害降低所有现代 JavaScrip引擎的性能</strong>。这篇文章解释了修改 <code>[[Prototype]]</code> 对象在<em>所有</em>浏览器中都很慢的原因并给出了替代方案。</p>
<h2 id="JavaScript引擎是如何提升访问对象属性的性能的">JavaScript引擎是如何提升访问对象属性的性能的</h2>
<p>Objects are hashes, 所以理论上来说 (实际上也是如此) 访问属性所花费的时间是恒定不变的.  但是 "恒定不变" 的背后也可能有成千上万的机器指令.  幸运的是, 大多数情况下对象和属性是"可预测的", 在这些情况下它们的底层结构也是可预测的.  即时编译器可以据此来减少对象属性的访问所花费时间。</p>
<p>引擎根据添加到对象的顺序属性进行优化。大多数属性都是按照非常相似的顺序添加到对象中的。(经常使用obj[val]风格的随机访问访问对象是一个明显的例外。)</p>
<pre><code  class="language-javascript">function Landmark(lat, lon, desc) {
  this.location = { lat: lat, long: lon };
  this.description = desc;
}
var lm1 = new Landmark(-90, 0, "South Pole");
var lm2 = new Landmark(-24.3756466, -128.311018, "Pitcairn Islands");</code></pre>
<p>在上面的例子中，每生成一个 <code>Landmark</code> 对象时都将按照 <code>location</code> 和 <code>description</code> 两个属性顺序加载，而存储“经度/纬度”信息的 location 也具有 lat 到 long 的顺序。随后的代码可以删除一个属性。但这是不可能的，因为引擎在这种情况下会生成一段不太理想的代码。在 SpiderMonkey （火狐的 JavaScript 引擎）里，属性的特定顺序（以及属性的其他一些方面，但不包括值）我们称之为“形状”（shape）（谷歌 V8 引擎里，这个概念名为“结构ID”(structure ID))。如果两个对象共享同一个 shape，那么他们属性的存储也相同。</p>
<p><code>Landmark</code> 对象在引擎内部的（简化）版本如同下面的 C++：</p>
<pre><code class="language-cpp">struct Property {
  Property* prev; // null if first property
  String name; // name of property
  unsigned int index; // index of its value in storage
};
using Shape = Property*;
struct Object {
  Shape shape;
  Value* properties;
  Object* prototype;
};</code></pre>
<p>例子中的JS表达式对应下面的 C++:</p>
<pre><code class="language-cpp">lm1-&gt;properties[0]; // loc1.location
lm1-&gt;properties[1]; // loc1.description
lm2-&gt;properties[0].toObject()-&gt;properties[1]; // loc2.location.long</code></pre>
<p>如果引擎知道一个对象具有特殊 shape，就可以根据 shape 假定这个对象所有属性的索引。这样一来进行一次访问特定属性，也就相当于几个指针访问所花费的时间。机器语言去检查对象是否具有特定 shape 也很容易，如果有，那么假定索引快速访问；如果没有，那么慢慢来。</p>
<h2 id="Naively_optimizing_inherited_properties">Naively optimizing inherited properties</h2>
<p>Many properties don't exist <em>directly</em> on the object: lookups often find properties on the prototype chain.  Accesses to properties on prototypes is just extra "hops" through the <code>prototype</code> field to the object containing the property.  Optimizing <em>correctly</em> requires that no object along the way have the property, so every hop must check that object's shape.</p>
<pre><code  class="language-javascript">var d = new Date();
d.toDateString(); // Date.prototype.toDateString

function Pair(x, y) { this.x = x; this.y = y; }
Pair.prototype.sum = function() { return this.x + this.y; };

var p = new Pair(3, 7);
p.sum(); // Pair.prototype.sum</code></pre>
<p>Engines take this quick-and-dirty approach in many cases.  But in especially performance-sensitive JavaScript, this isn't good enough.</p>
<h2 id="Intelligently_optimizing_inherited_properties">Intelligently optimizing inherited properties</h2>
<p>Predictable property accesses <em>usually</em> find the property a constant number of hops along the <code>[[Prototype]]</code> chain; intervening objects <em>usually</em> don't acquire new properties; the ultimate object <em>usually</em> won't have any properties <a href="/en-US/docs/Web/JavaScript/Reference/Operators/delete">deleted</a>.  Finally: <strong><code>[[Prototype]]</code> mutation is rare</strong>.  All these common assumptions are necessary to avoid slow prototype-hopping.  Different engines choose different approaches to intelligently optimize inherited properties.</p>
<dl>
<dt>The shape of the <em>ultimate</em> object containing the inherited can be checked.</dt>
<dd>In this case, a shape match must imply that no intervening object's <code>[[Prototype]]</code> has been modified.  Therefore, when an object's <code>[[Prototype]]</code> is mutated, every object along its <code>[[Prototype]]</code> chain must also have its shape changed.</dd>
<dd>
<pre><code  class="language-javascript">var obj1 = {};
var obj2 = Object.create(obj1);
var obj3 = Object.create(obj2);

// Objects whose shapes would change: obj3, obj2, obj1, Object.prototype
obj3.__proto__ = {};</code></pre>
</dd>
<dt>The shape of the object initially accessed can be checked.</dt>
<dd>Every object that might inherit through a changed-<code>[[Prototype]]</code> object must change, reflecting the <code>[[Prototype]]</code> mutation having happened</dd>
<dd>
<pre><code  class="language-javascript">var obj1 = {};
var obj2 = Object.create(obj1);
var obj3 = Object.create(obj2);

// Objects whose shapes would change: obj1, obj2, obj3
obj1.__proto__ = {};</code></pre>
</dd>
</dl>
<h2 id="Pernicious_effects_of_Prototype_mutation">Pernicious effects of <code>[[Prototype]]</code> mutation</h2>
<p><code>[[Prototype]]</code> mutation's adverse performance impact occurs in two phases: at the time mutation occurs, and in subsequent execution.  First, <strong>mutating <code>[[Prototype]]</code> is slow</strong>.  Second, <strong>mutating <code>[[Prototype]]</code> slows down code that interacts with mutated-<code>[[Prototype]]</code> objects</strong>.</p>
<h3 id="Mutating_Prototype_is_slow">Mutating <code>[[Prototype]] is slow</code></h3>
<p>While the spec considers mutating <code>[[Prototype]]</code> to be modifying a single hidden property, real-world implementations are considerably more complex.  Both shape-changing tactics described above require examining (and modifying) more than one object.  Which approach modifies fewer objects in practice, depends upon the workload.</p>
<h3 id="Mutated_Prototypes_slow_down_other_code">Mutated <code>[[Prototype]]</code>s slow down other code</h3>
<p>The bad effects of <code>[[Prototype]]</code> mutation don't end once the mutation is complete.  Because so many property-examination operations implicitly depend on <code>[[Prototype]]</code> chains not changing, when engines observe a mutation, <em>an object with mutated <code>[[Prototype]] "taints" all code the object flows through</code></em>.  This tainting flows through all code that ever observes a mutated-<code>[[Prototype]]</code> object.  As a near-worst-case illustration, consider these patterns of behavior:</p>
<pre><code  class="language-javascript">var obj = {};
obj.__proto__ = { x: 3 }; // gratuitous mutation

var arr = [obj];
for (var i = 0; i &lt; 5; i++)
  arr.push({ x: i });

function f(v, i) {
  var elt = v[i];
  var r =  elt.x &gt; 2 // pessimized
           ? elt
           : { x: elt.x + 1 };
  return r;
}
var c = f(arr, 0);
c.x; // pessimized: return value has unknown properties
c = f(arr, 1);
c.x; // still pessimized!

var arr2 = [c];
arr2[0].x; // pessimized
</code></pre>
<p>(Only code that runs many times is optimized, so this doesn't trigger <em>all</em> these bad behaviors.  But every breakdown could happen if it appeared in "hot" code.)</p>
<p>Recognizing exactly where a mutated-<code>[[Prototype]]</code> object flows, often across multiple scripts, is extraordinarily difficult.  It depends on careful textual analysis of the code and particular runtime behaviors.  Far-distant changes, that trigger subtly different control flow, can taint previously-optimal code paths with pessimal behavior.  <em>It's impossible to recognize all the places that will become slower, <strong>even for a JavaScript language implementer</strong>.</em></p>
<p>remaining constant.Mutation must, in addition to changing other objects' shapes,</p>
<p> </p>
<p>  But this requires storing <em>cross-object</em> information.</p>
<p>Cross-object information is different from shape, in that it can't easily be checked.  One modification to this information may affect many locations, none obviously connected to it: where to look to verify assumptions?  So instead of checking the assumptions before use, <em>all</em> code making assumptions is <em>invalidated</em> when a modification happens.  When a <code>[[Prototype]]</code> changes, <em>all</em> code depending on it must be thrown away.  The operation <code>obj.__proto__ = ...</code> is thus inherently slow.  And by throwing away already-optimized code, it makes that code much slower when it runs later.</p>
<p>But it's worse than that.  When evaluating <code>obj.prop</code> sees an object whose <code>[[Prototype]]</code> has been mutated, so much previously-known information about the object becomes useless that SpiderMonkey considers the object to have wholly-unknown characteristics.  Any code path that touches such an object in the future will assume the worst.  Optimizing JIT engines assume that <em>future execution is like past execution</em>.  If an object with mutated <code>[[Prototype]]</code> is observed by some code, that code will likely observe more such objects.  Therefore, <strong>operations that interact with an object with mutated <code>[[Prototype]]</code>, anywhere, in any scripts, are un-optimizable</strong>.</p>
<p>The un-optimizability of objects with mutated <code>[[Prototype]]</code> is not</p>
</article>